package com.fast.orm.jdbc;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONObject;
import com.fast.orm.aspect.ExpanderOccasion;
import com.fast.orm.aspect.ExpanderRunner;
import com.fast.orm.cache.DataCache;
import com.fast.orm.config.FastAttributes;
import com.fast.orm.data.DataPackage;
import com.fast.orm.exec.DeleteProvider;
import com.fast.orm.exec.InsertProvider;
import com.fast.orm.exec.SelectProvider;
import com.fast.orm.exec.UpdateProvider;
import com.fast.orm.logs.SqlPrintLog;
import com.fast.orm.many.JdbcJoinQueryRowMapper;
import com.fast.orm.mapper.PrimaryKeyType;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;

import java.util.*;

/**
 * SpringJDBC NamedParameterJdbcTemplate执行器实现
 *
 * @author 张亚伟 https://github.com/kaixinzyw
 */
public class JdbcImpl {
    public static List<Object> insert(DataPackage dataPackage) {
        try {
            execution(dataPackage);
            ExpanderRunner.runBeforeExpander(dataPackage, ExpanderOccasion.INSERT.method);
            InsertProvider.insert(dataPackage);
            List<Object> insertList = dataPackage.getInsertList();
            if (CollUtil.isNotEmpty(insertList) && dataPackage.getTableMapper().getPrimaryKeyType() != null && dataPackage.getTableMapper().getPrimaryKeyType().equals(PrimaryKeyType.AUTO)) {
                KeyHolder keyHolder = new GeneratedKeyHolder();
                FastAttributes.getJdbcTemplate().update(MySqlUtil.sqlConversion(dataPackage.getSql()), new MapSqlParameterSource(dataPackage.getParamMap()), keyHolder);
                if (insertList.size() == 1 && keyHolder.getKey() != null) {
                    BeanUtil.setFieldValue(insertList.get(0), dataPackage.getTableMapper().getPrimaryKeyField(), Objects.requireNonNull(keyHolder.getKey()).longValue());
                } else {
                    List<Map<String, Object>> keyList = keyHolder.getKeyList();
                    for (int i = 0; i < keyList.size(); i++) {
                        BeanUtil.setFieldValue(insertList.get(i), dataPackage.getTableMapper().getPrimaryKeyField(), Objects.requireNonNull(keyList.get(i).values().iterator().next()));
                    }
                }
            } else {
                FastAttributes.getJdbcTemplate().update(MySqlUtil.sqlConversion(dataPackage.getSql()), dataPackage.getParamMap());
            }
            DataCache.upCache(dataPackage);
            return extracted(dataPackage, dataPackage.getInsertList(), ExpanderOccasion.INSERT.method);
        } catch (Exception e) {
            SqlPrintLog.printSql(dataPackage);
            throw e;
        }
    }

    public static List<JSONObject> select(DataPackage dataPackage) {
        try {
            execution(dataPackage);
            ExpanderRunner.runBeforeExpander(dataPackage, ExpanderOccasion.SELECT.method);
            SelectProvider.findAll(dataPackage);
            DataCache<JSONObject> dataCache = null;
            if (dataPackage.getTableMapper().getOpenCache()) {
                dataCache = DataCache.init(dataPackage);
                List<JSONObject> list = dataCache.get();
                if (list != null) {
                    return list;
                }
            }
            List<JSONObject> resultList;
            if (CollUtil.isEmpty(dataPackage.getFastJoinQueryInfoList())) {
                resultList = FastAttributes.getJdbcTemplate().query(MySqlUtil.sqlConversion(dataPackage.getSql()),
                        dataPackage.getParamMap(),
                        new JdbcRowMapper());
            } else {
                Map<Object, Map<String, Map<Object, JSONObject>>> collectionData = new HashMap<>();
                resultList = FastAttributes.getJdbcTemplate().query(MySqlUtil.sqlConversion(dataPackage.getSql()),
                        dataPackage.getParamMap(), new JdbcJoinQueryRowMapper(dataPackage, collectionData));
                if (collectionData.size() > 0) {
                    Map<Object, JSONObject> dataGroup = new HashMap<>();
                    for (JSONObject jsonObject : resultList) {
                        dataGroup.put(jsonObject.get(dataPackage.getTableMapper().getPrimaryKeyTableField()), jsonObject);
                    }
                    List<JSONObject> temp = new ArrayList<>();
                    for (Object id : dataGroup.keySet()) {
                        Map<String, Map<Object, JSONObject>> fieldDataMap = collectionData.get(id);
                        JSONObject data = dataGroup.get(id);
                        if (fieldDataMap != null) {
                            for (String fieldName : fieldDataMap.keySet()) {
                                Map<Object, JSONObject> fieldData = fieldDataMap.get(fieldName);
                                List<JSONObject> fieldDataList = new ArrayList<>();
                                for (Object key : fieldData.keySet()) {
                                    fieldDataList.add(fieldData.get(key));
                                }
                                data.put(fieldName, fieldDataList);
                            }
                        }
                        temp.add(data);
                    }
                    resultList = temp;
                }
            }
            if (dataCache != null) {
                dataCache.set(resultList);
            }
            return extracted(dataPackage, resultList, ExpanderOccasion.SELECT.method);
        } catch (Exception e) {
            SqlPrintLog.printSql(dataPackage);
            throw e;
        }

    }

    public static Integer count(DataPackage dataPackage) {
        try {
            execution(dataPackage);
            ExpanderRunner.runBeforeExpander(dataPackage, ExpanderOccasion.COUNT.method);
            SelectProvider.findCount(dataPackage);
            DataCache<Object> dataCache = null;
            if (dataPackage.getTableMapper().getOpenCache()) {
                dataCache = DataCache.init(dataPackage);
                List count = dataCache.get();
                if (CollUtil.isNotEmpty(count)) {
                    return (Integer) count.get(0);
                }
            }
            Integer count = 0;
            if (StrUtil.isBlank(dataPackage.getGroupBy())) {
                count = FastAttributes.getJdbcTemplate().queryForObject(MySqlUtil.sqlConversion(dataPackage.getSql()), dataPackage.getParamMap(), Integer.class);
            } else {
                List<List> list = FastAttributes.getJdbcTemplate().queryForList(MySqlUtil.sqlConversion(dataPackage.getSql()), dataPackage.getParamMap(), List.class);
                if (CollUtil.isNotEmpty(list)) {
                    if (dataPackage.getPageNumber() != null) {
                        count = list.size();
                    } else {
                        for (List num : list) {
                            count += Integer.parseInt(num.get(0).toString());
                        }
                    }
                }
            }
            if (dataCache != null) {
                dataCache.set(CollUtil.newArrayList(count));
            }
            return extracted(dataPackage, count, ExpanderOccasion.COUNT.method);
        } catch (Exception e) {
            SqlPrintLog.printSql(dataPackage);
            throw e;
        }
    }

    public static Integer update(DataPackage dataPackage) {
        try {
            execution(dataPackage);
            ExpanderRunner.runBeforeExpander(dataPackage, ExpanderOccasion.UPDATE.method);
            UpdateProvider.update(dataPackage);
            int updateNum = FastAttributes.getJdbcTemplate().update(MySqlUtil.sqlConversion(dataPackage.getSql()), dataPackage.getParamMap());
            if (updateNum > 0) {
                DataCache.upCache(dataPackage);
            }
            return extracted(dataPackage,
                    updateNum,
                    ExpanderOccasion.UPDATE.method);
        } catch (Exception e) {
            SqlPrintLog.printSql(dataPackage);
            throw e;
        }
    }

    public static Integer delete(DataPackage dataPackage) {
        try {
            execution(dataPackage);
            ExpanderRunner.runBeforeExpander(dataPackage, ExpanderOccasion.DELETE.method);
            DeleteProvider.delete(dataPackage);
            int deleteNum = FastAttributes.getJdbcTemplate().update(MySqlUtil.sqlConversion(dataPackage.getSql()), dataPackage.getParamMap());
            if (deleteNum > 0) {
                DataCache.upCache(dataPackage);
            }
            return extracted(dataPackage,
                    deleteNum,
                    ExpanderOccasion.DELETE.method);
        } catch (Exception e) {
            SqlPrintLog.printSql(dataPackage);
            throw e;
        }

    }

    private static <T> T extracted(DataPackage dataPackage, T result, String methodName) {
        dataPackage.setReturnVal(result);
        ExpanderRunner.runAfterExpander(dataPackage, methodName);
        SqlPrintLog.printSql(dataPackage);
        return result;
    }

    private static void execution(DataPackage dataPackage) {
        dataPackage.setSqlTime(System.currentTimeMillis());
    }

}
